package handlers

import (
	"encoding/json"
	"fmt"
	"gitee.com/Cookie_XiaoD/go-filestore-server/cache/redis"
	"gitee.com/Cookie_XiaoD/go-filestore-server/db"
	"gitee.com/Cookie_XiaoD/go-filestore-server/db/entities"
	"gitee.com/Cookie_XiaoD/go-filestore-server/meta"
	"io"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	chunkSize float64 = 5 * 1024 * 1024
)

//InitMultipartUploadHandler 初始化分块上传
func InitMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {

	req := &initMultipartUploadReq{}
	err := json.NewDecoder(r.Body).Decode(req)
	if err != nil {
		SendJsonResp(w, BuildArgsErrResp())
		return
	}
	r.ParseForm()
	username := r.Form.Get("username")
	//初始化分块信息
	chunkInfo := multipartUploadResp{
		FileSize:   req.FileSize,
		FileHash:   req.FileHash,
		UploadID:   username + fmt.Sprintf("%x", time.Now().UnixNano()),
		ChunkSize:  int64(chunkSize),
		ChunkCount: int32(math.Ceil(float64(req.FileSize) / chunkSize)),
	}
	//利用map存储，方便更新分块上传情况
	mapInfo := make(map[string]interface{})
	mapInfo["fileSize"] = chunkInfo.FileSize
	mapInfo["fileHash"] = chunkInfo.FileHash
	mapInfo["uploadID"] = chunkInfo.UploadID
	mapInfo["chunkSize"] = chunkInfo.ChunkSize
	mapInfo["chunkCount"] = chunkInfo.ChunkCount

	err = redis.RedisClient().HMSet("MP_"+chunkInfo.UploadID, mapInfo).Err()

	if err != nil {
		log.Printf("redis设置分块数据异常：%v", err.Error())
		SendServerErrResp(w)
		return
	}

	SendJsonResp(w, BuildSuccessResp(chunkInfo))

}

//UploadPartHandler 上传文件分块
func UploadPartHandler(w http.ResponseWriter, r *http.Request) {
	if r.Body == nil {
		SendJsonResp(w, BuildArgsErrResp())
		return
	}

	log.Println(r.Body)

	r.ParseForm()
	userName := r.Form.Get("username")
	uploadID := r.Form.Get("uploadID")
	chunkIndex := r.Form.Get("chunkIndex")

	//创建本地文件 路径为/data/上传ID/分片索引
	partPath := "/data/" + uploadID + "/" + chunkIndex
	//当前用户读写执行权限  其他为读权限   ugo 执行为1 读为4 写为2
	//如果目录已经存在也返回nil
	err := os.MkdirAll(path.Dir(partPath), 0744)
	if err != nil {
		log.Printf("创建文件夹失败：%v", err.Error())
		SendServerErrResp(w)
		return
	}
	//目录创建成功，创建文件
	file, err := os.Create(partPath)
	if err != nil {
		log.Printf("创建文件失败：%v", err.Error())
		SendServerErrResp(w)
		return
	}
	defer file.Close()

	//读取body中的文件信息
	//利用缓存读取，每次读取1Mb,写入文件
	buf := make([]byte, 1024*1024)
	for {
		len, err := r.Body.Read(buf)
		//读取发生错误，且不是EOF错误
		if err != nil && err != io.EOF {
			log.Printf("上传文件分片时，发生错误%v", err.Error())
			SendServerErrResp(w)
			break
		}
		//请求体无body则len为0
		if len == 0 {
			SendJsonResp(w, BuildArgsErrResp())
			return
		}
		file.Write(buf[:len])
		log.Printf("写入文件 字节：%v", len)

		if err == io.EOF {
			log.Printf("文件读取完毕%v", err.Error())
			break
		}
	}

	//更新redis 当前uploadid上传分片进度
	err = redis.RedisClient().HSet("MP_"+uploadID, "chunkidx_"+chunkIndex, 1).Err()
	if err != nil {
		log.Printf("redis操作失败：%v", err.Error())
		SendServerErrResp(w)
		return
	}

	log.Printf("username:%v uploadID：%v chunkIndex:%v", userName, uploadID, chunkIndex)
	SendJsonResp(w, BuildSuccessResp(nil))
}

//CompleteUploadHandler 完成分块文件上传
func CompleteUploadHandler(w http.ResponseWriter, r *http.Request) {
	//1.解析参数 得到上传任务ID 文件大小 文件哈希 文件名

	req := &completeUploadReq{}
	err := json.NewDecoder(r.Body).Decode(req)
	if err != nil {
		SendJsonResp(w, BuildArgsErrResp())
		return
	}
	r.ParseForm()
	userName := r.Form.Get("username")

	//2.检查redis中当前上传任务 分块是否都已经上传
	result, err := redis.RedisClient().HGetAll("MP_" + req.UploadID).Result()
	if err != nil {
		SendServerErrResp(w)
		return
	}
	totalCount := 0
	chunkCount := 0
	for k, v := range result {
		if k == "chunkCount" {
			totalCount, _ = strconv.Atoi(v)
		} else if strings.HasPrefix(k, "chunkidx_") {
			chunkCount++
		}
	}

	if totalCount != chunkCount {
		SendJsonResp(w, BuildFailResp(BlockIncompleteCode, BlockIncompleteMsg))
		return
	}

	//3.分块都上传完毕 ，则进行分块合并，合并为完整的文件
	//按照顺序读取每个分块文件，最后按照实际文件名重新保存一份
	filePath := "../../UploadFiles/" + req.FileName
	if err := os.MkdirAll(path.Dir(filePath), 0744); err != nil {
		SendServerErrResp(w)
		return
	}

	file, err := os.Create(filePath)
	if err != nil {
		SendServerErrResp(w)
		return
	}
	defer file.Close()

	for i := 0; i < totalCount; i++ {
		partFilePath := "/data/" + req.UploadID + "/" + strconv.Itoa(i)
		partFileBytes, err := ioutil.ReadFile(partFilePath)
		if err != nil {
			SendServerErrResp(w)
			return
		}
		if _, err = file.Write(partFileBytes); err != nil {
			SendServerErrResp(w)
			return
		}
	}

	//4.更新文件信息表
	upResult := meta.UpdateFileMetaDB(&meta.FileMeta{
		FileHash: req.FileHash,
		FileName: req.FileName,
		FileSize: req.FileSize,
		Location: filePath,
		UploadAt: time.Now().Format("2006-01-02 15:04:05"),
	})

	if !upResult {
		SendServerErrResp(w)
		return
	}
	//5.更新用户文件表
	upResult = db.AddUserFileInfo(&entities.AddUserFileEntity{
		UserName: userName,
		FileName: req.FileName,
		FileSize: req.FileSize,
		FileHash: req.FileHash,
	})
	if !upResult {
		SendServerErrResp(w)
		return
	}

	SendJsonResp(w, BuildSuccessResp(nil))
}
